package chapter08;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.IntFunction;

/**
 * @Author: insight
 * @Description: 泛型的限制与局限性
 * @LastDate: 2020/12/3
 */
public class Test06_Limitations {

    public static void main(String[] args) {

    }

    /**
     * 注意类型擦除后的冲突
     */
    public static void conflictAfterErase() {
        class Pair<T> {
            //'equals(T)' in 'Pair' clashes with 'equals(Object)' in 'java.lang.Object'; both methods have same erasure, yet neither overrides the other
            //类型擦除后 与父类Object的equals冲突
            /*boolean equals(T val) {
                return false;
            }*/
        }

        //可以实现多个泛型接口
        class Empl implements Comparable<Empl>, Task<Integer> {

            @Override
            public int compareTo(Empl o) {
                return 0;
            }

            @Override
            public void run() throws Exception {

            }
        }
        /*
        * Duplicate class: 'java.lang.Comparable'
        * 不能实现同一接口的不同参数化
        * 至于原因嘛，是因为有可能与前面提到的桥方法冲突 P336
        * 子类实现泛型接口时 为保证多态 会在子类生成桥方法
        * (比如子类赋给父类对象 然后父类调用方法 我们希望调用的是子类的方法)
        * 如果可以实现多个泛型接口 那么就会在子类生成多个桥方法
        * 也就是说有多个方法首部一模一样 冲突
        */
        /*class Emple implements Comparable<Empl>, Comparable<Integer> {

            @Override
            public int compareTo(Empl o) {
                return 0;
            }

            @Override
            public void run() throws Exception {

            }
        }*/
    }

    /**
     * 取消对检查型异常的检查
     */
    public static void cancelCheckingForException() {
        //正常情况下 要求你捕获所有检查型异常
        //因为run方法不允许抛出检查型异常
        /*var thread = new Thread(() -> {
            Thread.sleep(1000);
            System.out.println("wake up");
            throw new Exception("Warn!");
        });*/
        //可以通过泛型 变成非检查型异常
        //欺骗编译器
        var newThread = new Thread(Task.asRun(
                () -> {
                    Thread.sleep(1000);
                    System.out.println("wake up");
                    throw new Exception("Warn!");
                }
        ));
    }

    /**
     * catch泛型实例
     * 不能catch
     * 不过可以使用
     */
    static <T extends Throwable> void work() throws Throwable {
        //甚至不能拓展throwable
        // Generic class may not extend 'java.lang.Throwable'
        // class Problem<T> extends Throwable { }

        //不能catch
        /*try {

        } catch (T e) {
            //err Cannot catch type parameters
            //不能catch 更别说throw了
            throw e;
            e.printStackTrace();
        }*/
        //可以使用
        try {

        } catch (Throwable e) {
            T newE = null;
            throw newE;
        }
    }

    /**
     * 泛型类中 不能在泛型变量或方法使用静态修饰符
     */
    public static void invalidTypeVarInGenericClass() {
        class invalidTypeVarInGenericClass<T> {
            /*private static T singleInstance;//error
            private static T getInstance {
                if (singleInstance == null) {
                    //
                }
                return singleInstance;
            }*/
        }
    }

    public static <T extends Comparable> void generateGenericArray(IntFunction<T[]> constr, T a) {
        /*
         * 构造泛型数组
         * 泛型数组也有类型 有类型就逃不过类型擦除
         * 类型擦除后 有可能会发生类型转换错误
         * ClassCastException
         *
         * 好的做法是 提供数组构造器表达式
         * 使用表达式生成一个正确类型的数组
         */
        T[] ret = constr.apply(2);

        //也可以使用反射
        T[] ret2 = (T[]) Array.newInstance(a.getClass().getComponentType(), 2);
    }

    /**
     * 不能实例化一个泛型变量
     */
    public static void cantInstantiateGenericVar() {
        /*
         * 关于实例化一个泛型变量new T()
         * public Pair(T first) { this.first = new T(); }//error
         * 因为类型擦除后就变成new Object() 但我们明明传进了一个明确的类型
         * 所以 正确的做法(After Java 8)是
         * 让调用者提供一个构造器表达式 Type::new
         * Pair#makePair
         */
        var pair = Pair.makePair(String::new);
        /*
         * 函数签名
         * <T> Pair<T> makePair(Supplier<T> con)
         * 接收一个Supplier<T> 这是一个函数式接口 表示一个无参数且返回类型为T的函数
         * 只有一个方法 get()
         *
         * 也可以使用反射来生成实例
         * var t = T.class().getConstructor().newInstance();//err
         * 不过同理也是擦除后也是得到Object.class 还是不行
         * 正确的反射做法是
         * <T> Pair<T> makePair(Class<T> cl) {
         *     return Pair<>(cl.getConstructor().newInstance());
         * }
         * 然后调用
         * var pair = Pair.makePair(String.class);
         * 为什么它就可以？
         * 因为String.class会提供一个Class<String>实例，所以擦除后能得到正确的类型
         */
    }

    /**
     * 向一个参数数量可变的方法传入泛型实例
     */
    public static void variableParamsMethond() {
        var table = new ArrayList<Pair<Integer>>();
        Pair<Integer> p1 = new Pair<>();
        p1.setFirst(1);
        Pair<Integer> p2 = new Pair<>();
        p2.setFirst(2);
        Test06_Limitations.addGenericToArray(table, p1, p2);
    }

    /**
     * 调用这个方法时 势必会创建一个参数化的数组
     * 这就违反了规则
     * 但是此时不太一样 只是一个warning
     * 那么就可以抑制warning
     * suppresswarning or savevarargs(Java7)
     *  SuppressWarnings("unchecked")
     */
    @SafeVarargs
    public static <T> void addGenericToArray(ArrayList<T> colls, T... ts) {
        Collections.addAll(colls, ts);
    }

    public static void createParameterizedArray() {
        /*
         * 这句会报错
         * 原因同样在于类型擦除
         * 擦除后变成Pair[] 可以转换成Object[]
         * 此时存储其他类型元素 就会报错
         */
        // var arr = new Pair<Integer>[];
        /*
         * 尽管能通过编译器的检查
         * 但是依旧会导致类型错误
         * 出于这个原因，不允许创建参数化类型的数组
         * 不过可以看得出来 只是不允许创建
         * 声明还是可以的
         */
        Pair<String>[] a;
        Object[] oarr = new Object[5];
        oarr[1] = new Pair<Integer>();

        //warning
        var table = (Pair<String>[]) new Pair<?>[10];
        //正确的做法
        var arr = new ArrayList<Pair<Integer>>();
    }

    public static void onlyOriginalType() {
        /*
         * 类型查询时 只会使用原始类型
         * Inconvertible types
         */
        var a = new Pair<Integer>();
        /*
          Pair != Pair<Double>
          var b = (Pair<Double>)a;
        if (a instanceof Pair<Double>) {

        }*/
        //同理 获取类型自然得到原始类型
        System.out.println(a.getClass());
    }

    public static void notUseBasicType() {
        /*
         * 不能使用基本类型实例化泛型
         * 原因在于 类型擦除后 类型变成Object
         * 而基本类型不属于Object
         */
        // List<int> list = new ArrayList<>();
    }
}

interface Task<T> {
    void run() throws Exception;

    //消除unchecked cast
    @SuppressWarnings("unchecked")
    static <T extends Throwable> void throwAs(Throwable t) throws T {
        // Unchecked cast: 'java.lang.Throwable' to 'T'
        throw (T) t;
    }

    static Runnable asRun(Task task) {
        return () -> {
          try {
              task.run();
          } catch (Exception e) {
              //RuntimeException 非检查型异常
              Task.throwAs(e);
          }
        };
    }
}